package sim.ring.method;

import java.util.Arrays;
import java.util.Vector;

import org.apache.log4j.*;


/**
 * @author Amoeba
 *
 */
public class Method {
	private int id;
	private String title;
	private String name;
	private int stage;
	private String classification;
	private String shortNotation;
	private String longNotation="";
	private int placeMethod;
	private int ringCount;
	private int currentChange=0;
	private int currentRow=1; //row 0 == rounds
	private Vector<int[]> method= new Vector<int[]>();
	private Vector<int[]> rows= new Vector<int[]>();
	private String[] changes;

	
	private Logger log = Logger.getLogger("sim.ring.method");
	
	public Method() {
		this.id=-1;
		this.rows=new Vector<int[]>();
	}
	
	/**
	 * Constructor for newly entered methods, either by hand or parsed from XML.
	 * Handles construction of full-length place notation and constructs initial 
	 * data structure to hold all rows for plain course
	 * 
	 * ringCount initially set to 0
	 * @param name name of the method (excluding stage definition)
	 * @param classification classification of the method
	 * @param stage stage (number of bells) on which the method is rung
	 * @param shortNotation shortened place notation
	 */
	public Method(String name, String classification, int stage, String shortNotation) {
		this.id=-1;
		this.name=name;
		this.classification=classification;
		this.stage=stage;	
		this.shortNotation=shortNotation;
		this.ringCount=0;
		
		int[] initialRow=new int[stage];
		for (int i=0;i<stage;i++) 
			initialRow[i]=i+1;
		method.add(initialRow);
		rows.add(initialRow);
		
		constructLongNotation();
		constructAllRows();
	}

	/**
	 * Constructor for methods loaded from database.  Constructs initial 
	 * data structure to hold all rows for plain course
	 * @param id id from database key field
	 * @param name name of the method (excluding stage definition)
	 * @param classification classification of the method
	 * @param stage stage (number of bells) on which the method is rung
	 * @param shortNotation shortened place notation
	 * @param longNotation full-length place notation
	 * @param ring count number of times this method has been rung
	 */
	public Method(int id, String name, String classification, int stage, String shortNotation, String longNotation, int ringCount) {
		//loaded methods
		this.id=id;
		this.name=name;
		this.classification=classification;
		this.stage=stage;	
		this.shortNotation=shortNotation;
		this.longNotation=longNotation;
		this.ringCount=ringCount;
		changes=longNotation.split("\\.");
		placeMethod=Integer.parseInt(Character.toString(((longNotation.charAt(longNotation.length()-1)))));
		
		int[] initialRow=new int[stage];
		for (int i=0;i<stage;i++) 
			initialRow[i]=i+1;
		method.add(initialRow);
		rows.add(initialRow);
		
		constructAllRows();
	}
	
	/**
	 * Gets the name of the method excluding stage definition<br>
	 * eg Plain Bob
	 * @return method's name
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Gets the full title of the method<br>
	 * eg Plain Bob Doubles 
	 * @return method's title
	 */
	public String getTitle() {
		return title;
	}
	
	
	/**
	 * Gets stage, ie number of bells on which the method is rung
	 * @return method's stage
	 */
	public int getStage() {
		return stage;
	}
	
	/**
	 * Gets the classification of the method<br>
	 * eg Plain, Treble Bob etc
	 * @return method's classification
	 */
	public String getClassification() {
		return classification;
	}
	
	/**
	 * Gets shortened place notation for the method, usually what is entered
	 * either from keyboard or read in from XML file
	 * @return method's shortened notation
	 */
	public String getShortNotation() {
		return shortNotation;
	}
	
	
	/**
	 * Gets full-length place notation for the method, constructed internally from
	 * the shortened place notation
	 * @return method's full-length place notation
	 */
	public String getLongNotation() {
		return longNotation;
	}
	
	/**
	 * Gets the database id for this method
	 * @return method's database id
	 */
	public int getId() {
		return id;
	}
	
	/**
	 * Sets the database id for this method
	 * @param id database id
	 */
	public void setId(int id) {
		this.id=id;
	}
	
	
	/**
	 * Gets the number of times this method has been 'rung' 
	 * @return number of times rung
	 */
	public int getRingCount() {
		//TODO: this should be linked to a given tower
		return ringCount;
	}
	
	
	/**
	 * Increments total ring count by 1
	 * @return the new ring count
	 */
	public int incrementRingCount() {
		//TODO: this should be linked to a given tower
		return ++ringCount;
	}
	
	
	/**
	 * Gets the next row due to be rung in the method.  Used when
	 * iterating through the method row by row.
	 * @return the next row
	 */
	public int[] getRowToRing() {
		constructNextRow();
		return rows.elementAt(currentRow++);
	}
	
	/**
	 * Gets the next change to action against the current row.<br>
	 * eg 'X', '14', etc<br>
	 * Note: this will only return a change once per lead
	 * @return the next change 
	 */
	public String getNextChange() {
		return changes[currentChange++];
	}
	
	/**
	 * Returns true if there are more changes for the current lead, returns
	 * false if the lead is complete.
	 * @return true if more leads in method
	 */
	public boolean hasMoreChanges() {
		if(currentChange==changes.length)
			return false;
		return true;
	}
	
	
	/**
	 * Simulates call of a bob during ringing.  Will attempt to work out affect of bob 
	 * 'automatically' and apply to place notation, unless alternate place notation has been
	 * manually defined.
	 */
	public void callBob() {
		/* TODO	currently only works for 2nds and nth place methods, needs vast extension!
				might be good to only attempt to 'work out' bob if no alternate place 
				notation specified (database + keyboard entry)
				*SHOULDN'T CHANGE 'longNotation' member as this will bugger up the next ring,
				need to have alternative variable!*
		*/
		String le="";
		if(placeMethod==2) {
			le="14";
		} else {
			le="1"+(placeMethod-2);
		}
		longNotation=longNotation.substring(0, longNotation.lastIndexOf(".x.")+3)+le;
		changes=longNotation.split("\\.");
	}
	
	/**
	 * Gets the name for the method's stage.<br>
	 * eg 5 -> Doubles<br>
	 * 	  6 -> Minor<br>
	 *    etc
	 * @return method's stage name
	 */
	public String getStageText() {
		switch(stage) {
		case 4: return "Minimus";
		case 5: return "Doubles";
		case 6: return "Minor";
		case 7: return "Triples";
		case 8: return "Major";
		case 9: return "Cinques";
		case 10: return "Royal";
		case 11: return "Caters";
		case 12: return "Maximus";
		default: return "Unknown";
		}
	}
	

	
	/**
	 * Builds the full place notation from the given short notation
	 */
	private void constructLongNotation() {
		String places="";
		
		if(shortNotation.contains(",")) {
			String[] tokens=shortNotation.split(",");
			places=tokens[0];
			String le=tokens[1];
			places=places.replaceAll("[x-]", ".x.");
			//if(places.charAt(0) == '&') {
			places=places.substring(1);
			if(places.startsWith("."))
				places=places.substring(1);
			if(places.endsWith("."))
				places=places.substring(0,places.length()-1);
			String[] shortChanges=places.split("\\.");
			changes=new String[(shortChanges.length*2)];
			for(int i=0;i<shortChanges.length;i++) {
				changes[i]=shortChanges[i];
				changes[changes.length-i-2]=shortChanges[i];
			}
			changes[changes.length-1]=le;
		} else {
			changes=places.split("\\.");
		}
		for(int i=0;i<changes.length-1;i++) 
			longNotation=longNotation+changes[i]+".";
		String le=changes[changes.length-1];
		longNotation=longNotation+le;
		placeMethod=Integer.parseInt(Character.toString(((le.charAt(le.length()-1)))));
	}
	
	
	/**
	 * Constructs entirety of method's collection of rows in a plain course
	 */
	private void constructAllRows() {
		int row[]=Arrays.copyOf(rows.lastElement(), stage);
		
		do {
			while(hasMoreChanges()) {
				String change = getNextChange();
				
				for(int bell=0;bell<stage;bell++) {
					if(change.contains(Integer.toString(bell+1))  ||
							change.contains(Integer.toString(bell+1)) ||
							change.equals("")) {
						continue;
					} else {
						swap(row, bell,++bell);				
					}
				}
				method.add(Arrays.copyOf(row, stage));
			}
			nextLead();
		} while(!isRounds(row));
		nextLead();
	}
	
	
	/**
	 * Constructs next row in the current lead.  Will be affected by any bobs / singles called
	 */
	private void constructNextRow() {
		int row[]=Arrays.copyOf(rows.lastElement(), stage);
		
		//TODO: this needs to be better
		if(!hasMoreChanges() && !isRounds(rows.lastElement()))
			nextLead();
		
		String change = getNextChange();
		
		for(int bell=0;bell<stage;bell++) {
			if(change.contains(Integer.toString(bell+1))  ||
					change.contains(Integer.toString(bell+1)) ||
					change.equals("")) {
				continue;
			} else {
				swap(row, bell,++bell);
			}
		}
		rows.add(Arrays.copyOf(row, stage));
	}
	
	
	/**
	 * Swaps bells in <code>bell1</code>'s and <code>bell2</code>'s position in the
	 * given row
	 * @param row row of bells in which change is to take palce
	 * @param bell1 position of bell 1's place
	 * @param bell2 position of bell 2's place
	 */
	private void swap(int[] row, int bell1, int bell2) {
		//TODO: ensure that bell1 and bell2 are next to each other!
		int temp=row[bell1];
		row[bell1]=row[bell2];
		row[bell2]=temp;
	}
	
	
	/**
	 * Resets position in place notation to beginning, ie beginning a new lead
	 */
	private void nextLead() {
		currentChange=0;
	}
	
	
	/**
	 * Returns true if the given row is 'rounds'
	 * @param row row of bells to check 
	 * @return true if rounds, false if not
	 */
	private boolean isRounds(int[] row) {
		boolean rounds = true;
		int pos=1;
		for (int bell : row) {
			if(bell!=pos++)
				rounds=false;
		}
		return rounds;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return name+" "+classification+" "+getStageText()+"\n"+shortNotation+"\n"+longNotation;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

}
	